/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package loadbalancer;

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.Reader;
import java.io.StringWriter;
import java.io.Writer;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.Socket;
import java.net.URL;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.logging.Level;

import server.thread.Request;

/**
 *
 * @author Guilherme
 */
public class Load implements Runnable
{
    private Socket socket;
    private LoadBalancer lbalancer;
    private Request r;
    
    protected static final DateFormat HTTP_DATE_FORMAT = new SimpleDateFormat(
            "EEE, dd MMM yyyy HH:mm:ss z");
    
    public Load(LoadBalancer lb, Socket socket)
    {
        this.lbalancer = lb;
        this.socket = socket;
    }
    
    public void run() 
    {
        InetAddress infoCliente;
        
        InputStream input = null;
        OutputStream output = null;
        
        this.r = new Request();
        
        
        try {
            infoCliente = socket.getInetAddress();
            
            input = socket.getInputStream();
            
            String request = this.requestToString(input);
            int typeOfRequest = this.getTypeOfRequest(request);
            System.out.println("[i] Client " + infoCliente.getHostName() + " connected\n[#]Content\n" + request);
            
            // verificar qual eh o tipo e enviar ao servidor destino
            if(typeOfRequest == 1) // POST
            {
                int destServer = this.lbalancer.computeDestinationServer(this.getStackName(request));
                this.processPOSTReq(infoCliente.getHostAddress(), this.lbalancer.getAddressServer(destServer), this.getDataRequest(request));
            } 
            else if(typeOfRequest == 0) // GET
            {
                this.r.parse(request);
                this.processGETReq(infoCliente.getHostAddress(), this.lbalancer.getDestinationWebServerAddress() + this.getGETResourceReq(request));
            }
            else
            {
                throw new Exception("[i] Invalid request");
            }
            
            input.close();
            output.close();
        } catch (java.io.IOException ex) {
            System.out.println("[X] Connection error: " + ex.getMessage());
        } catch (Exception ex){}
        finally 
        {
            try 
            {
                socket.close();
            } catch (java.io.IOException ex) {
                System.out.println("[X] Error when closing a socket thread: " + ex.getMessage());
            }
        }
    }
    
    /**
     * 
     * @param req
     * @return 1 se for uma requisição do tipo POST
     *         0 se for uma requisição do tipo GET
     *         -1 caso contrário
     */
    public int getTypeOfRequest(String req)
    {
        if((req!= null) && (!req.isEmpty()))
        {
            String type = req.substring(0,4);
            return type.equals("POST") ? 1 : (type.substring(0,3).equals("GET") ? 0 : -1);
        }
        return -1;
    }
    
    public String getStackName(String req)
    {
        int i, j;
        String reqData = this.getDataRequest(req);
        for(i = 0; i < reqData.length() && !reqData.startsWith("&name=", i); i++);
        for(j = i + 6; j < reqData.length() && reqData.charAt(j) != '&'; j++);
        return reqData.substring(i + 6, j);
    }
    
    public String getDataRequest(String req)
    {
        int i;
        for(i = 0; i < req.length() && !req.startsWith("Content-Length:", i); i++);
        for( ; i < req.length() && req.charAt(i) != '\n'; i++);
        i++;
        return req.substring(i + 1);
    }
    
    public String getGETResourceReq(String req)
    {
        int i;
        for(i = 4 ; i < req.length() && req.charAt(i) != '?'; i++);
        return req.substring(4, i);
    }
    
    public String requestToString(InputStream is) throws IOException
    {
//        BufferedReader in = new BufferedReader(new InputStreamReader(input));
//        String req = "";
//        while (in.ready()) {
//            req += in.readLine() + "\n";
//        }
//        return req;
        
        if (is != null) {
            Writer writer = new StringWriter();

            char[] buffer = new char[2048];
            try {
                Reader reader = new BufferedReader(new InputStreamReader(is));
                int i = reader.read(buffer);
                writer.write(buffer, 0, i);
            } catch (IOException e) {
                System.out.println(Level.SEVERE + "\n\tErro ao converter stream para string \n" + e);
                return "";
            }
            return writer.toString();
        } else {
            return "";
        }
    }
    
    public void processPOSTReq(String clientAddress, String serverAddress, String dataRequest) throws MalformedURLException, IOException
    {
        URL obj = new URL(serverAddress);
        HttpURLConnection con = (HttpURLConnection) obj.openConnection();

        //add reuqest header
        con.setRequestMethod("POST");
        con.setRequestProperty("User-Agent", "Mozilla/5.0");
        con.setRequestProperty("Accept-Language", "en-US,en;q=0.5");

        // Send post request
        con.setDoOutput(true);
        DataOutputStream wr = new DataOutputStream(con.getOutputStream());
        wr.writeBytes(dataRequest);
        wr.flush();
        wr.close();
        
        int responseCode = con.getResponseCode();
        if (responseCode == 200)
        {
            BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
            String inputLine;
            StringBuffer response = new StringBuffer();

            while ((inputLine = in.readLine()) != null)
            {
                response.append(inputLine);
            }
            in.close();
            wr = new DataOutputStream(socket.getOutputStream());
            wr.write(getHead("200 OK","text/html; charset=UTF-8").getBytes());
            wr.write(response.toString().getBytes());
            wr.flush();
            wr.close();
        }
        System.out.println("\nSending 'POST' request to URL : " + serverAddress);
        System.out.println("Post parameters : " + dataRequest);
        System.out.println("Response Code : " + responseCode);
    }
    
    public void processGETReq(String clientAddress, String resourceRequest) throws MalformedURLException, IOException
    {
        URL obj = new URL(resourceRequest);
        HttpURLConnection con = (HttpURLConnection) obj.openConnection();

        //add reuqest header
        con.setRequestMethod("GET");
        con.setRequestProperty("User-Agent", "Mozilla/5.0");
        con.setRequestProperty("Accept-Language", "en-US,en;q=0.5");

        // Send post request
        con.setDoOutput(true);
        DataOutputStream wr = new DataOutputStream(con.getOutputStream());
        //wr.writeBytes(dataRequest);
        wr.flush();
        wr.close();
        
        int responseCode = con.getResponseCode();
        if (responseCode == 200)
        {
            BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
            String inputLine;
            StringBuffer response = new StringBuffer();

            while ((inputLine = in.readLine()) != null)
            {
                response.append(inputLine);
            }
            in.close();
            wr = new DataOutputStream(socket.getOutputStream());
            wr.write(getHead("200 OK",defineTipo(this.r.getUri().trim())).getBytes());
            wr.write(response.toString().getBytes());
            wr.flush();
            wr.close();
        }
        System.out.println("\nSending 'GET' request to URL : " + resourceRequest);
//        System.out.println("Post parameters : " + dataRequest);
        System.out.println("Response Code : " + responseCode);
    }
    
    private String getHead(String codErro,String tipoRequisicao)
    {
        // Head
        StringBuilder sb = new StringBuilder();
        // Cria primeira linha do status code, no caso 200 OK
        sb.append("HTTP/1.1 " + codErro).append("\r\n");

        // Cria os cabeçalhos
        sb.append("Date: ").append(HTTP_DATE_FORMAT.format(new Date()))
                .append("\r\n");
        sb.append("Server: Web Server")
                .append("\r\n");
        sb.append("Connection: Close").append("\r\n");
        sb.append("Content-Type: " + tipoRequisicao).append("\r\n");
        sb.append("\r\n");
        return sb.toString();
    }
    
        private String defineTipo(String file) {
        /**
         * Mais tipo acesse em:
         * http://en.wikipedia.org/wiki/MIME_type#List_of_common_media_subtype_prefixes
         */
        String tipo = "";
        int i = (file.length() - 1);
        for ( ; i > 0;  i--) {
            if (file.charAt(i) == '.') {
                break;
            }
        }
        
        System.out.println("»»» Tipo do arquivo requisitado: "+file.substring(i));
        switch(file.substring(i)) {
            // tipo de Arquivos
            case ".http": tipo = "text/html; charset=UTF-8"; break;
            case ".css" : tipo = "text/css; charset=UTF-8"; break;
            case ".js" : tipo = "text/javascript; charset=UTF-8"; break;
            // tipos de Imagens
            case ".gif": tipo = "image/gif;"; break;
            case ".jpg" : tipo = "image/jpeg;"; break;
            case ".jpeg" : tipo = "image/jpeg;"; break;
            case ".png": tipo = "image/png"; break;
            
            default: tipo = "text/html; charset=UTF-8";
        }
        return tipo;
    }
}
